#include <qua/text.h>
#include <qua/cast.h>
#include <qua/error.h>
#include <qua/fixed.h>
#include <qua/time.h>
#include <qua/stdfwd.h>
#include <cstdio>
#include <cstddef>
#include <cstring>

namespace qua
{
    const text TEXT_NULL  = "null";
    const text TEXT_TRUE  = "true";
    const text TEXT_FALSE = "false";

    const char* CAST_FORMAT_INT64   = "%lld";
    const char* CAST_FORMAT_UINT64  = "%llu";
    const char* CAST_FORMAT_INT32   = "%ld";
    const char* CAST_FORMAT_UINT32  = "%lu";
    const char* CAST_FORMAT_INT16   = "%hd";
    const char* CAST_FORMAT_UINT16  = "%hu";
    const char* CAST_FORMAT_FLOAT   = "%G";
    const char* CAST_FORMAT_DOUBLE  = "%LG";

    const char* CAST_FORMAT_TIME = "%Y-%m-%d %H:%M:%S";

    const char* CAST_FORMAT_TIME_DELTA = CAST_FORMAT_INT64;

    const char* CAST_FORMATS[] = {
        CAST_FORMAT_INT64,
        CAST_FORMAT_UINT64,
        CAST_FORMAT_INT32,
        CAST_FORMAT_UINT32,
        CAST_FORMAT_INT16,
        CAST_FORMAT_UINT16,
        CAST_FORMAT_FLOAT,
        CAST_FORMAT_DOUBLE,
    };

    const int CAST_FORMAT_ID_INT64  = 0;
    const int CAST_FORMAT_ID_UINT64 = 1;
    const int CAST_FORMAT_ID_INT32  = 2;
    const int CAST_FORMAT_ID_UINT32 = 3;
    const int CAST_FORMAT_ID_INT16  = 4;
    const int CAST_FORMAT_ID_UINT16 = 5;
    const int CAST_FORMAT_ID_FLOAT  = 6;
    const int CAST_FORMAT_ID_DOUBLE = 7;

    template< typename T >
    struct cast_format;

    template<> struct cast_format<int64_t>  { static const int ID = CAST_FORMAT_ID_INT64;   static const size_t MAX_SIZE =  20; };
    template<> struct cast_format<uint64_t> { static const int ID = CAST_FORMAT_ID_UINT64;  static const size_t MAX_SIZE =  20; };
    template<> struct cast_format<int32_t>  { static const int ID = CAST_FORMAT_ID_INT32;   static const size_t MAX_SIZE =  10; };
    template<> struct cast_format<uint32_t> { static const int ID = CAST_FORMAT_ID_UINT32;  static const size_t MAX_SIZE =  10; };
    template<> struct cast_format<int16_t>  { static const int ID = CAST_FORMAT_ID_INT16;   static const size_t MAX_SIZE =   5; };
    template<> struct cast_format<uint16_t> { static const int ID = CAST_FORMAT_ID_UINT16;  static const size_t MAX_SIZE =   5; };
    template<> struct cast_format<float>    { static const int ID = CAST_FORMAT_ID_FLOAT;   static const size_t MAX_SIZE =  40; };
    template<> struct cast_format<double>   { static const int ID = CAST_FORMAT_ID_DOUBLE;  static const size_t MAX_SIZE = 400; };

    template< typename T >
    const char* cast_format_of()
    {
        return CAST_FORMATS[ cast_format<T>::ID ];
    }

    text cast_to<text>::from( std::nullptr_t )
    {
        return TEXT_NULL;
    }

    text cast_to<text>::from( bool value )
    {
        return value ? TEXT_TRUE : TEXT_FALSE;
    }

    bool cast_to<bool>::from( text const& value )
    {
        if( value == TEXT_TRUE )
            return true;
        else if( value == TEXT_FALSE )
            return false;
        else
            throw error( "Unable to recognize boolean in the text: " + value, __FILE__, __LINE__ );
    }

    void throw_out_of_range_error( const char* target_type_name, const char* source_type_name )
    {
        throw error( "Unable to convert out of range value of type " + text(source_type_name) + " to the type " + text(target_type_name), __FILE__, __LINE__ );
    }

    template< typename T >
    text scalar_to_text( T value, const char* format = nullptr )
    {
        if( !format ) format = cast_format_of<T>();
        char buffer[ cast_format<T>::MAX_SIZE ];
        int count = _snprintf( buffer, sizeof(buffer), format, value );
        if( !count )
            throw error( text("Unable to convert to text value of type ") + typeid(T).name(), __FILE__, __LINE__ );
        return text( buffer );
    }

    template< typename T >
    T text_to_scalar( text const& value, const char* format = nullptr )
    {
        T result;
        if( !format ) format = cast_format_of<T>();
        int count =  sscanf( value.to_utf8(), format, &result );
        if( !count )
            throw error( text("Unable to read from text value of type ") + typeid(T).name(), __FILE__, __LINE__ );
        return result;
    }

    text cast_to<text>::from( uint8_t value, const char* format )
    {
        return scalar_to_text( static_cast<uint16_t>( value ), format );
    }

    text cast_to<text>::from( int8_t value, const char* format )
    {
        return scalar_to_text( static_cast<int16_t>( value ), format );
    }

    text cast_to<text>::from( uint16_t value, const char* format )
    {
        return scalar_to_text( value, format );
    }

    text cast_to<text>::from( int16_t value, const char* format )
    {
        return scalar_to_text( value, format );
    }

    text cast_to<text>::from( uint32_t value, const char* format )
    {
        return scalar_to_text( value, format );
    }

    text cast_to<text>::from( int32_t value, const char* format )
    {
        return scalar_to_text( value, format );
    }

    text cast_to<text>::from( uint64_t value, const char* format )
    {
        return scalar_to_text( value, format );
    }

    text cast_to<text>::from( int64_t value, const char* format )
    {
        return scalar_to_text( value, format );
    }

    text cast_to<text>::from( float value, const char* format )
    {
        return scalar_to_text( value, format );
    }

    text cast_to<text>::from( double value, const char* format )
    {
        return scalar_to_text( value, format );
    }

    uint8_t cast_to<uint8_t>::from( text const& value, const char* format )
    {
        return static_cast<uint8_t>( text_to_scalar<uint16_t>( value, format ) );
    }

    int8_t cast_to<int8_t>::from( text const& value, const char* format )
    {
        return static_cast<int8_t>( text_to_scalar<int16_t>( value, format ) );
    }

    uint16_t cast_to<uint16_t>::from( text const& value, const char* format )
    {
        return text_to_scalar<uint16_t>( value, format );
    }

    int16_t cast_to<int16_t>::from( text const& value, const char* format )
    {
        return text_to_scalar<int16_t>( value, format );
    }

    uint32_t cast_to<uint32_t>::from( text const& value, const char* format )
    {
        return text_to_scalar<uint32_t>( value, format );
    }

    int32_t cast_to<int32_t>::from( text const& value, const char* format )
    {
        return text_to_scalar<int32_t>( value, format );
    }

    uint64_t cast_to<uint64_t>::from( text const& value, const char* format )
    {
        return text_to_scalar<uint64_t>( value, format );
    }

    int64_t cast_to<int64_t>::from( text const& value, const char* format )
    {
        return text_to_scalar<int64_t>( value, format );
    }

    float cast_to<float>::from( text const& value, const char* format )
    {
        return text_to_scalar<float>( value, format );
    }

    double cast_to<double>::from( text const& value, const char* format )
    {
        return text_to_scalar<double>( value, format );
    }

    class fixed_to_text
    {
    public:
        static const char* default_format()
        {
            static fixed_to_text instance;
            return instance.m_format;
        }

    private:
        char m_format[10];

        fixed_to_text()
        {
            int count = _snprintf( m_format, sizeof(m_format), "%%0%ldllu", static_cast<int32_t>( FIXED_MAX_PRECISION ) );
            if( !count )
                throw error( "Unable to make default format for fixed type.", __FILE__, __LINE__ );
        }
    };

    text cast_to<text>::from( fixed const& value, const char* format )
    {
        if( !format ) format = fixed_to_text::default_format();
        int64_t  lex_int;
        uint64_t lex_frac;
        if( value.floor() >= 0 || !value.mulfrac() )
            lex_int = value.floor(), lex_frac = value.mulfrac();
        else
            lex_int = value.floor() + 1, lex_frac = fixed::divider() - value.mulfrac();
        if( value.prec() < 0 )
        {
            if( !value.frac() )
                return cast<text>( lex_int );
            else
            {
                text dec = cast<text>( lex_frac, format );
                if( dec.empty() )
                    throw error( "Non-zero fractional part of fixed value cast to text as empty string.", __FILE__, __LINE__ );
                const char* utf8_str = dec.to_utf8();
                size_t last = dec.size() - 1;
                for( ; last && utf8_str[last] != '0'; --last );
                dec.resize( last );
                return cast<text>( lex_int ) + '.' + dec;
            }
        }
        else if( !value.prec() )
        {
            return cast<text>( lex_int );
        }
        else
        {
            int p = FIXED_MAX_PRECISION - value.prec();
            return cast<text>( lex_int ) + '.' + cast<text>( ( lex_frac + FIXED_HALF_POW10[p] ) / FIXED_POW10[p] );
        }
    }

    text cast_to<text>::from( time const& value, const char* format )
    {
        if( !format ) format = CAST_FORMAT_TIME;
        text result;
        bool format_tag = false;
        for( const char* pchar = format; *pchar; ++pchar )
        {
            if( !format_tag )
            {
                if( *pchar == '%' )
                    format_tag = true;
                else
                    result += *pchar;
            }
            else
            {
                format_tag = false;
                switch( *pchar )
                {
                case 'Y':
                    result += cast<text>( value.year(), "%04d" );
                    break;
                case 'y':
                    result += cast<text>( value.year() % 100, "%02d" );
                    break;
                case 'm':
                    result += cast<text>( value.month(), "%02d" );
                    break;
                case 'd':
                    result += cast<text>( value.day(), "%02d" );
                    break;
                case 'H':
                    result += cast<text>( value.hour(), "%02d" );
                    break;
                case 'M':
                    result += cast<text>( value.minute(), "%02d" );
                    break;
                case 'S':
                    result += cast<text>( value.second(), "%02d" );
                    break;
                case 'f':
                    result += cast<text>( value.microsecond(), "%06d" );
                    break;
                case '%':
                    result += '%';
                    break;
                default:
                    result += '%';
                    result += *pchar;
                    break;
                }
            }
        }
        return result;
    }

    fixed cast_to<fixed>::from( text const& value, const char* format )
    {
        int64_t lex_int;
        char buffer[ 64 ];
        static const size_t max_possible_size = 40;
        if( value.size() > max_possible_size )
        	throw error( "Unable to read fixed value, text too long: " + value, __FILE__, __LINE__ );
        int count = sscanf( value.to_utf8(), "%lld.%s", &lex_int, buffer );
        // result:
        fixed result( lex_int );
        switch( count )
        {
        case 2:
            {
                uint64_t lex_frac;
                int len = static_cast<int>( strlen( buffer ) );
                for( ; len < FIXED_MAX_PRECISION; ++len ) buffer[len] = '0';
                buffer[len] = 0;
                sscanf( buffer, "%llu", &lex_frac );
                result.set_mulfrac( lex_frac );
            }
            break;
        case 1: // already well-initialized
            break;
        default:
            throw error( "Unable to read fixed value from text " + value, __FILE__, __LINE__ );
        }
        return result;
    }

    time cast_to<time>::from( text const& value, const char* format )
    {
        if( !format ) format = CAST_FORMAT_TIME;
        int Y=0, m=0, d=0, H=0, M=0, S=0, f=0;
        bool format_tag = false;
        text::const_iterator it = value.begin();
        for( const char* pchar = format; *pchar; ++pchar )
        {
            if( !format_tag )
            {
                if( *pchar == '%' )
                    format_tag = true;
                else
                    ++it;
            }
            else
            {
                format_tag = false;
                switch( *pchar )
                {
                case 'Y':
                    Y = cast<int>( value.substr( it, it+4 ) );
                    it += 4;
                    break;
                case 'y':
                    Y = 2000 + cast<int>( value.substr( it, it+2 ) );
                    it += 2;
                    break;
                case 'm':
                    m = cast<int>( value.substr( it, it+2 ) );
                    it += 2;
                    break;
                case 'd':
                    d = cast<int>( value.substr( it, it+2 ) );
                    it += 2;
                    break;
                case 'H':
                    H = cast<int>( value.substr( it, it+2 ) );
                    it += 2;
                    break;
                case 'M':
                    M = cast<int>( value.substr( it, it+2 ) );
                    it += 2;
                    break;
                case 'S':
                    S = cast<int>( value.substr( it, it+2 ) );
                    it += 2;
                    break;
                case 'f':
                    f = cast<int>( value.substr( it, value.end() ) );
                    it = value.end();
                    break;
                case '%':
                    ++it;
                    break;
                default:
                    break;
                }
            }
        }
        if( !Y || !m || !d )
            throw error( "Not set three values of date: %Y or %y - year, %m - month, %d - day. Unable to convert value '"
                        + value + "' with format: " + text(format), __FILE__, __LINE__ );
        return time( Y, m, d, H, M, S, f );
    }

    template<>
    fixed cast_to<fixed>::from( bool value )
    {
        return fixed( cast<int>( value ) );
    }

    template<>
    fixed cast_to<fixed>::from( int8_t value )
    {
        return fixed( value );
    }

    template<>
    fixed cast_to<fixed>::from( int16_t value )
    {
        return fixed( value );
    }

    template<>
    fixed cast_to<fixed>::from( int32_t value )
    {
        return fixed( value );
    }

    template<>
    fixed cast_to<fixed>::from( int64_t value )
    {
        return fixed( value );
    }

    template<>
    fixed cast_to<fixed>::from( uint8_t value )
    {
        return fixed( value );
    }

    template<>
    fixed cast_to<fixed>::from( uint16_t value )
    {
        return fixed( value );
    }

    template<>
    fixed cast_to<fixed>::from( uint32_t value )
    {
        return fixed( value );
    }

    template<>
    fixed cast_to<fixed>::from( uint64_t value )
    {
        return fixed( value );
    }

    template<>
    fixed cast_to<fixed>::from( float value )
    {
        return fixed( value );
    }

    template<>
    fixed cast_to<fixed>::from( double  value )
    {
        return fixed( value );
    }
}
